﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Samples.Helpers;
using System.IO;
using System.Text.RegularExpressions;

public class SceneReader
{
	const string unityYamlDocumentTag = "--- !u!";
	const string yamlDocumentTag = "---";
	const string typeInvalid = "InvalidType";

	public List<DocPrimitive> primitiveList { get; set; }

	private static SceneReader instance;

	private SceneReader ()
	{
	}

	public static SceneReader Instance {
		get {
			if (instance == null)
				instance = new SceneReader ();

			return instance;
		}
	}

	public void Init ()
	{
		primitiveList = new List<DocPrimitive> ();
	}

	public void Parse (string path)
	{
		TextReader reader = File.OpenText (path);

		string content = reader.ReadToEnd ();
		string[] documents = Regex.Split (content, unityYamlDocumentTag);

		for (int i = 1; i < documents.Length; ++i) {
			int type;
			uint id;
			ParseTypeAndId (documents [i], out type, out id);
			ParseYamlDocument (documents [i], type, id);
		}
	}

	public void ParseString (string content)
	{
		string[] documents = Regex.Split (content, unityYamlDocumentTag);

		for (int i = 1; i < documents.Length; ++i) {
			int type;
			uint id;
			ParseTypeAndId (documents [i], out type, out id);
			ParseYamlDocument (documents [i], type, id);
		}
	}

	void ParseTypeAndId (string doc, out int type, out uint id)
	{
		StringReader reader = new StringReader (doc);

		string line = reader.ReadLine ();

		string[] content = line.Split ('&');
		type = int.Parse (content [0].Trim ());
		if (!content [1].Contains ("stripped")) {
			id = uint.Parse (content [1].Trim ());
		} else {
			//process stripped game object specially
			if (type == (int)PrimitiveType.GameObject)
				type = (int)PrimitiveType.GameObjectStripped;
			else if (type == (int)PrimitiveType.Transform)
				type = (int)PrimitiveType.TransformStripped;
			
			string temp = content [1].Replace ("stripped", string.Empty);
			id = uint.Parse (temp.Trim ());
		}

		reader.Dispose ();
	}

	void ParseYamlDocument (string doc, int type, uint id)
	{
		StringReader reader = new StringReader (doc);

		//跳过第一行
		reader.ReadLine ();

		//拼装成yamlDotNet格式
		string other = reader.ReadToEnd ();
		reader.Dispose ();

		string whole = yamlDocumentTag + "\n" + other;
		string content = whole;

		DocPrimitive dp;
		string strType = typeInvalid;

		//解析这个object
		switch (type) {
		//GameObject
		case ((int)PrimitiveType.GameObject):
			strType = "GameObjectDoc";
			break;
		case ((int)PrimitiveType.GameObjectStripped):
			strType = "GameObjectStrippedDoc";
			break;
		//MonoBehaviour
		case ((int)PrimitiveType.MonoBehaviour):
			strType = "MonoBehaviourDoc";
                //who can tell me how to deal with this elegantly?
			content = ProcessToNormalMonoBehaviour (whole);
			break;
		//Transform
		case ((int)PrimitiveType.Transform):
			strType = "TransformDoc";
			break;
		//Behaviour: Flare Layer
		case ((int)PrimitiveType.FlaryLayer):
		case ((int)PrimitiveType.GUILayer):
			strType = "BehaviourDoc";
			break;
		//Camera
		case ((int)PrimitiveType.Camera):
			strType = "CameraDoc";
			break;
		case ((int)PrimitiveType.MeshCollider):
			strType = "MeshColliderDoc";
			break;
		case ((int)PrimitiveType.BoxCollider):
			strType = "BoxColliderDoc";
			break;
		case ((int)PrimitiveType.SphereCollider):
			strType = "SphereColliderDoc";
			break;
		case ((int)PrimitiveType.CapsuleCollider):
			strType = "CapsuleColliderDoc";
			break;
		case ((int)PrimitiveType.MeshFilter):
			strType = "MeshFilterDoc";
			break;
		case ((int)PrimitiveType.MeshRenderer):
			strType = "MeshRendererDoc";
			break;
		case ((int)PrimitiveType.Prefab):
			strType = "PrefabDoc";
			break;
		case ((int)PrimitiveType.Animation):
			strType = "AnimationDoc";
			break;
		case ((int)PrimitiveType.Animator):
			strType = "AnimatorDoc";
			break;
		case ((int)PrimitiveType.TransformStripped):
			strType = "TransformStrippedDoc";
			break;
		}

		dp = DeserializeGameObject (content, strType) as DocPrimitive;

		if (dp != null) {
			dp.type = type;
			dp.id = id;
			primitiveList.Add (dp);

			if (dp.type == (int)PrimitiveType.MonoBehaviour) {
				dp.extension = GetMonoBehaviourExtension (whole);
			}
		}
	}

	string ProcessToNormalMonoBehaviour (string monoBehaviourDoc)
	{
		StringReader reader = new StringReader (monoBehaviourDoc);
		string s = reader.ReadLine ();

		// 8 isn't magic number here, we need ignore those lines
		for (int i = 0; i < 8; ++i) {
			s += "\n";
			s += reader.ReadLine ();
		}

		return s;
	}

	string GetMonoBehaviourExtension (string monoBehaviourDoc)
	{
		StringReader reader = new StringReader (monoBehaviourDoc);

		for (int i = 0; i < 11; ++i) {
			reader.ReadLine ();
		}

		return reader.ReadToEnd ();
	}

	DocPrimitive DeserializeGameObject (string content, string strType)
	{
		if (strType == typeInvalid) {
			StringReader reader = new StringReader (content);
			reader.ReadLine ();
			Debug.LogWarning ("还不支持的类名：" + reader.ReadLine ());
			return null;
		}

		var input = new StringReader (content);

		var deserializer = new DeserializerBuilder ()
            .WithNamingConvention (new CamelCaseNamingConvention ())
            .Build ();

		System.Type classType = System.Type.GetType (strType);
		if (classType != null)
			return deserializer.Deserialize (input, classType) as DocPrimitive;
		else {
			Debug.LogError ("指定的类名在assembly中不存在：" + strType);
			return null;
		}
	}
}
